//import com.sun.scenario.effect.impl.sw.sse.SSEBlend_ADDPeer;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Scanner;
public class DES {
    /* 定义置换表 */
    /* 初始置换表 */
    static int[] IP = {
            58, 50, 42, 34, 26, 18, 10, 2,
            60, 52, 44, 36, 28, 20, 12, 4,
            62, 54, 46, 38, 30, 22, 14, 6,
            64, 56, 48, 40, 32, 24, 16, 8,
            57, 49, 41, 33, 25, 17, 9, 1,
            59, 51, 43, 35, 27, 19, 11, 3,
            61, 53, 45, 37, 29, 21, 13, 5,
            63, 55, 47, 39, 31, 23, 15, 7
    };
    /* 逆初始置换表 */
    static int[] IP_I = {
            40, 8, 48, 16, 56, 24, 64, 32,
            39, 7, 47, 15, 55, 23, 63, 31,
            38, 6, 46, 14, 54, 22, 62, 30,
            37, 5, 45, 13, 53, 21, 61, 29,
            36, 4, 44, 12, 52, 20, 60, 28,
            35, 3, 43, 11, 51, 19, 59, 27,
            34, 2, 42, 10, 50, 18, 58, 26,
            33, 1, 41, 9, 49, 17, 57, 25
    };
    /* 选择扩展运算矩阵 E —— 将输入的32位数据扩展为48位与密钥进行异或 */
    static int[] E = {
            32, 1, 2, 3, 4, 5,
            4, 5, 6, 7, 8, 9,
            8, 9, 10, 11, 12, 13,
            12, 13, 14, 15, 16, 17,
            16, 17, 18, 19, 20, 21,
            20, 21, 22, 23, 24, 25,
            24, 25, 26, 27, 28, 29,
            28, 29, 30, 31, 32, 1
    };
    /* 选择压缩运算 S 盒 —— 将异或的结果划分八组，进行六输入四输出，48位输入32位输出*/
    static int[][][] S = {
            {
                    {14, 4, 13, 1, 2, 15, 11, 8, 3, 10, 6, 12, 5, 9, 0, 7},
                    {0, 15, 7, 4, 14, 2, 13, 1, 10, 6, 12, 11, 9, 5, 3, 8},
                    {4, 1, 14, 8, 13, 6, 2, 11, 15, 12, 9, 7, 3, 10, 5, 0},
                    {15, 12, 8, 2, 4, 9, 1, 7, 5, 11, 3, 14, 10, 0, 6, 13}
            },
            {
                    {15, 1, 8, 14, 6, 11, 3, 4, 9, 7, 2, 13, 12, 0, 5, 10},
                    {3, 13, 4, 7, 15, 2, 8, 14, 12, 0, 1, 10, 6, 9, 11, 5},
                    {0, 14, 7, 11, 10, 4, 13, 1, 5, 8, 12, 6, 9, 3, 2, 15},
                    {13, 8, 10, 1, 3, 15, 4, 2, 11, 6, 7, 12, 0, 5, 14, 9}
            },
            {
                    {10, 0, 9, 14, 6, 3, 15, 5, 1, 13, 12, 7, 11, 4, 2, 8},
                    {13, 7, 0, 9, 3, 4, 6, 10, 2, 8, 5, 14, 12, 11, 15, 1},
                    {13, 6, 4, 9, 8, 15, 3, 0, 11, 1, 2, 12, 5, 10, 14, 7},
                    {1, 10, 13, 0, 6, 9, 8, 7, 4, 15, 14, 3, 11, 5, 2, 12}
            },
            {
                    {7, 13, 14, 3, 0, 6, 9, 10, 1, 2, 8, 5, 11, 12, 4, 15},
                    {13, 8, 11, 5, 6, 15, 0, 3, 4, 7, 2, 12, 1, 10, 14, 9},
                    {10, 6, 9, 0, 12, 11, 7, 13, 15, 1, 3, 14, 5, 2, 8, 4},
                    {3, 15, 0, 6, 10, 1, 13, 8, 9, 4, 5, 11, 12, 7, 2, 14}
            },
            {
                    {2, 12, 4, 1, 7, 10, 11, 6, 8, 5, 3, 15, 13, 0, 14, 9},
                    {14, 11, 2, 12, 4, 7, 13, 1, 5, 0, 15, 10, 3, 9, 8, 6},
                    {4, 2, 1, 11, 10, 13, 7, 8, 15, 9, 12, 5, 6, 3, 0, 14},
                    {11, 8, 12, 7, 1, 14, 2, 13, 6, 15, 0, 9, 10, 4, 5, 3}
            },
            {
                    {12, 1, 10, 15, 9, 2, 6, 8, 0, 13, 3, 4, 14, 7, 5, 11},
                    {10, 15, 4, 2, 7, 12, 9, 5, 6, 1, 13, 14, 0, 11, 3, 8},
                    {9, 14, 15, 5, 2, 8, 12, 3, 7, 0, 4, 10, 1, 13, 11, 6},
                    {4, 3, 2, 12, 9, 5, 15, 10, 11, 14, 1, 7, 6, 0, 8, 13}
            },
            {
                    {4, 11, 2, 14, 15, 0, 8, 13, 3, 12, 9, 7, 5, 10, 6, 1},
                    {13, 0, 11, 7, 4, 9, 1, 10, 14, 3, 5, 12, 2, 15, 8, 6},
                    {1, 4, 11, 13, 12, 3, 7, 14, 10, 15, 6, 8, 0, 5, 9, 2},
                    {6, 11, 13, 8, 1, 4, 10, 7, 9, 5, 0, 15, 14, 2, 3, 12}
            },
            {
                    {13, 2, 8, 4, 6, 15, 11, 1, 10, 9, 3, 14, 5, 0, 12, 7},
                    {1, 15, 13, 8, 10, 3, 7, 4, 12, 5, 6, 11, 0, 14, 9, 2},
                    {7, 11, 4, 1, 9, 12, 14, 2, 0, 6, 10, 13, 15, 3, 5, 8},
                    {2, 1, 14, 7, 4, 10, 8, 13, 15, 12, 9, 0, 3, 5, 6, 11}
            }
    };
    /* 置换运算矩阵 P —— 接收32位输出，进行置换*/
    static int[] P = {
            16, 7, 20, 21,
            29, 12, 28, 17,
            1, 15, 23, 26,
            5, 18, 31, 10,
            2, 8, 24, 14,
            32, 27, 3, 9,
            19, 13, 30, 6,
            22, 11, 4, 25
    };
    /* 密钥交换选择表 C0D0 —— 接收64位密钥并将8位奇偶校验去除，并打乱56位密钥顺序 */
    static int[] C0 = {
            57, 49, 41, 33, 25, 17, 9,
            1, 58, 50, 42, 34, 26, 18,
            10, 2, 59, 51, 43, 35, 27,
            19, 11, 3, 60, 52, 44, 36
    };
    static int[] D0 = {
            63, 55, 47, 39, 31, 23, 15,
            7, 62, 54, 46, 38, 30, 22,
            14, 6, 61, 53, 45, 37, 29,
            21, 13, 5, 28, 20, 12, 4
    };

    /* 循环左移位数表 LS */
    static int[] LS = {
            1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1
    };

    /* PC-2 选择矩阵 —— 从56位输入选择48位生成子密钥 */
    static int[] PC2 = {
            14, 17, 11, 24, 1, 5,
            3, 28, 15, 6, 21, 10,
            23, 19, 12, 4, 26, 8,
            16, 7, 27, 20, 13, 2,
            41, 52, 31, 37, 47, 55,
            30, 40, 51, 45, 33, 48,
            44, 49, 39, 56, 34, 53,
            46, 42, 50, 36, 29, 32
    };

    /* 一些函数实现 */

    /*
     *  动态字符串转换二进制
     * 基于字符串内容进行转换
     * 方便密钥输入以及将明文转换为二进制字符串 */
    public static StringBuffer stringBufferToBinary(StringBuffer s) {
        StringBuffer res = new StringBuffer();
        for (int i = 0; i < s.length(); i++) {
            StringBuffer stmp = new StringBuffer(Integer.toBinaryString(s.charAt(i)));
            while (stmp.length() % 8 != 0) {  //转成8位二进制
                stmp.insert(0, 0);
            }
            res.append(stmp);
        }
        return res;
    }

    /*
     * 二进制转换为字符
     * 基于16位二进制转换为字符串内容 */
//    public static char binaryToChar(StringBuffer s) {
//        String s1 = new String(s);
//        int sum = 0;
//        for (int i = 0; i < s.length(); i++) {
//                sum = Integer.parseInt(s1, 2);
//        }
//        return (char)sum;
//    }
    public static StringBuffer binaryTostringBuffer(StringBuffer s) {
        StringBuffer res = new StringBuffer();
        for (int i = 0; i < s.length() / 8; i++) {
            int t = Integer.parseInt(s.substring(i * 8, (i + 1) * 8), 2);
            res.append((char) t);
        }
        return res;
    }

    /*   字节转换为二进制
     *   基于存储形式进行转换  */
    public static StringBuffer byteTOBit(byte s) {
//        StringBuffer res = new StringBuffer(Integer.toBinaryString(s));
//        while (res.length() % 8 != 0) {
//            res.insert(0, 0);
//        }
//        return res;
        StringBuffer res = new StringBuffer();
        for (int cnt = 0; cnt < 8; cnt++) {
            res.insert(0, (s >> cnt) & 1);
//            res.append((s >> cnt) & 1);
        }
        return res;
    }
    /*  二进制转换为字节    */
    public static byte bitTOByte(StringBuffer s) {
        int t = 0;
        for (int i = 0; i < s.length() / 8; i++) {
            t = Integer.parseInt(s.substring(i * 8, (i + 1) * 8), 2);

        }
        return (byte) t;

    }
    /*  将长度为8字节的字符串转换为二进制   */
    public static StringBuffer byte8TOBit64(byte[] bytes) {
        StringBuffer res = new StringBuffer();
        for (int cnt = 0; cnt < 8; cnt++) {
            res.append(byteTOBit(bytes[cnt]));
        }
        return res;
    }
    /*  将二进制位串转为字节长度为8的字符串    */
    public static byte[] bit64TOByte8(StringBuffer s) {
//        StringBuffer res = new StringBuffer();
        byte[] res = new byte[8];
        for (int cnt = 0; cnt < 8; cnt++) {
            res[cnt] = bitTOByte(new StringBuffer(s.substring(cnt * 8, cnt * 8 + 8)));
        }
        return res;
    }

    /*  二进制转换为十进制   */
    public static int binaryToDecimal(String str) {
        int len = str.length();
        int res = 0;
        for (int i = 0; i < len; i++) {
            res += Integer.parseInt(String.valueOf(str.charAt(len - i - 1))) * Math.pow(2, i);
        }
        return res;
    }

    /*  异或运算    */
    public static StringBuffer XOR(StringBuffer L, StringBuffer R) throws Exception {
        if (L.length() != R.length()) {
            throw new Exception("异或运算的两个数据串长度不同");
        }
        StringBuffer xor = new StringBuffer();
        for (int i = 0; i < R.length(); i++) {
            xor.append(L.charAt(i) ^ R.charAt(i));
        }
        return xor;
    }
    /*
     * DSE 算法实现
     * */

    /* 密钥生成 */
    public static StringBuffer[] subKeyGeneration(StringBuffer key) throws Exception {
        StringBuffer keyBinary = new StringBuffer(stringBufferToBinary(key)); //把密钥转成二进制
        StringBuffer[] subkey = new StringBuffer[16];  //subkey数组用来存储子密钥
        StringBuffer C = new StringBuffer(); //左密钥
        StringBuffer D = new StringBuffer(); //右密钥
        if (keyBinary.length() != 64) {
            throw new Exception("密钥长度不是64位");
        }

        /*  PC-1 置换
         * 去除密钥中的奇偶校验位，并将顺序打乱，划分为长度相等的两部分 */
        for (int i = 0; i < 28; i++) {
            C.append(keyBinary.charAt(C0[i] - 1));
            D.append(keyBinary.charAt(D0[i] - 1));
        }
        /*  进行16轮循环迭代
         *   每轮迭代左移LS[i]位    */
        for (int i = 0; i < 16; i++) {
            int shiftBits = LS[i];
//            根据循环左移表进行移位操作
//            System.out.println(C);
            for (int j = 0; j < shiftBits; j++) {
                char temp = C.charAt(0);
                C.deleteCharAt(0);
                C.append(temp);
                temp = D.charAt(0);
                D.deleteCharAt(0);
                D.append(temp);
            }
//            执行合并操作，C + D ，并将合并结果作为PC - 2的输入,得到第i轮的子密钥
            StringBuffer C0D0 = new StringBuffer(C.toString() + D.toString());
            StringBuffer C0D0tmp = new StringBuffer();
            for (int j = 0; j < 48; j++) {
                C0D0tmp.append(C0D0.charAt(PC2[j] - 1));
            }
            subkey[i] = C0D0tmp;
        }
        return subkey;
    }
    /*  初始 IP 置换    */
    public static StringBuffer initialPermuation(StringBuffer s) {
        StringBuffer res = new StringBuffer();
        for (int i = 0; i < IP.length; i++) {
            res.append(s.charAt(IP[i] - 1));
        }
        return res;
    }
    /*  逆初始IP置换     */
    public static StringBuffer inverseInitialPermuation(StringBuffer s) {
        StringBuffer res = new StringBuffer();
        for (int i = 0; i < IP_I.length; i++) {
            res.append(s.charAt(IP_I[i] - 1));
        }
        return res;
    }
    /*  乘积变换
     *   16轮迭代
     *   Li = R i-1
     *   R i = L i-1 异或 F(R i-1, Ki)
     *   F 加密函数  包括选择扩展运算E   选择压缩运算S   置换运算P */
    /*  F 函数运算 */
    public static StringBuffer function(StringBuffer R, StringBuffer subkey) throws Exception {
        /*  扩展运算    32bit -> 48bits */
        StringBuffer extendedR = new StringBuffer();
        for (int i = 0; i < E.length; i++) {
            extendedR.append(R.charAt(E[i] - 1));
        }
        /*  扩展后的R 与子密钥 K 进行异或运算 */
        if (extendedR.length() != subkey.length()) {
            throw new Exception("异或运算的两个数据串长度不同");
        }
        StringBuffer xor = new StringBuffer();
        for (int i = 0; i < extendedR.length(); i++) {
            xor.append(extendedR.charAt(i) ^ subkey.charAt(i));
        }
        /*  异或运算后的结果进行S盒压缩
         *   48bits -> 32bits
         *   b1b6 -> 行数， b2b3b4b5 -> 列数  */
        StringBuffer shrink = new StringBuffer();
        for (int i = 0; i < 8; i++) {
            String str = new String();
            str = xor.substring(i * 6, 6 * (i + 1));
            int x = binaryToDecimal(str.substring(0, 1) + str.substring(5, 6));
            int y = binaryToDecimal(str.substring(1, 5));
            //  从S盒取S[i][x][y]转化为4位输出
            String s = Integer.toBinaryString(S[i][x][y]);
            StringBuffer stringBuffer = new StringBuffer(s);
            //  如果输出未达到4位则往前补0，补齐4位
            while (4 - stringBuffer.length() != 0) {
                stringBuffer.insert(0, '0');
            }
            shrink.append(stringBuffer);
        }
        /*  P 置换运算  */
        StringBuffer res = new StringBuffer();
        for (int i = 0; i < P.length; i++) {
            res.append(shrink.charAt(P[i] - 1));
        }
        return res;
    }
    /*  加密单个分组  */
    public static StringBuffer Encryption(StringBuffer Plaintext, StringBuffer Key) throws Exception {
        //  获取子密钥
        StringBuffer[] subKeys = subKeyGeneration(Key);
        //  IP置换
//        StringBuffer str = new StringBuffer(stringBufferToBinary(Plaintext)); //把明文转成二进制
        StringBuffer stringBuffer = initialPermuation(Plaintext);
        StringBuffer L = new StringBuffer(stringBuffer.substring(0, 32));
        StringBuffer R = new StringBuffer(stringBuffer.substring(32, 64));
        //3.for循环16轮
        for (int i = 0; i < 16; i++) {
            //  F 函数
            StringBuffer f_output = function(R, subKeys[i]);
            //  异或
            StringBuffer temp = XOR(L, f_output);
            //  更新Li和Ri
            L = R;
            R = temp;
        }
        //  左右互换
        StringBuffer res = R.append(L);
        //  逆IP置换
        res = inverseInitialPermuation(res);
//        byte[] res2 = bit64TOByte8(res);
//        System.out.println("加密：" + res);
        return res;
    }
    /*  解密单个分组  */
    public static StringBuffer Decryption(StringBuffer ciphertext, StringBuffer Key) throws Exception {
        //  获取子密钥
        StringBuffer[] subKeys = subKeyGeneration(Key);
        //  IP置换
//        StringBuffer str = new StringBuffer(stringBufferToBinary(ciphertext)); //把秘文转成二进制
        StringBuffer stringBuffer = initialPermuation(ciphertext);
        StringBuffer L = new StringBuffer(stringBuffer.substring(0, 32));
        StringBuffer R = new StringBuffer(stringBuffer.substring(32, 64));
        //  for循环16轮
        for (int i = 15; i >= 0; i--) {
            //  f函数
            StringBuffer f_output = function(R, subKeys[i]);
            //  异或
            StringBuffer temp = XOR(L, f_output);
            //  更新Li和Ri
            L = R;
            R = temp;
        }
        //  左右互换
        StringBuffer res = R.append(L);
        //  逆IP置换
        res = inverseInitialPermuation(res);
//        byte[] res2 = bit64TOByte8(res);
//        System.out.println(res);
        return res;
    }
    /*  分组加密    */
    public static StringBuffer EncryptionGroup(StringBuffer plaintext, StringBuffer key) throws Exception {
        /*  对明文进行补充    */
        int Group = plaintext.length() / 4;
//        plaintext = stringBufferToBinary(plaintext); // 明文转二进制
        while (stringBufferToBinary(plaintext).length() < 64 * (Group + 1)) {
            plaintext.append("\0");
        }
        plaintext = stringBufferToBinary(plaintext); // 明文转二进制
        /*  分组加密    */
        String Split = new String();
        StringBuffer plaintextSplit = new StringBuffer();
        StringBuffer EncryptionText = new StringBuffer();
        for (int i = 0; i < plaintext.length() / 64; i++) {
//            System.out.println("i = " +  i);
//            System.out.println(plaintext.length());
            Split = plaintext.substring(i * 64, (i + 1) * 64);
            plaintextSplit = new StringBuffer(Split);
//            System.out.println("加密分组为：" + binaryTostringBuffer(plaintextSplit));
            EncryptionText.append(Encryption(plaintextSplit, key));
        }
//        System.out.println(EncryptionText);
        return EncryptionText;
    }
    /*  分组解密    */
    public static StringBuffer DecryptionGroup(StringBuffer cipher, StringBuffer key) throws Exception {
        StringBuffer ciphertext = stringBufferToBinary(cipher); // 密文转二进制
//        System.out.println("密文二进制：" + ciphertext);
        String Split = new String();
        StringBuffer ciphertextSplit = new StringBuffer();
        StringBuffer DecryptionText = new StringBuffer();
        for (int i = 0; i < ciphertext.length() / 64; i++) {
            Split = ciphertext.substring(i * 64, (i + 1) * 64);
            ciphertextSplit = new StringBuffer(Split);
//            System.out.println("解密分组为：" + ciphertextSplit);
            DecryptionText.append(Decryption(ciphertextSplit, key));
        }
        return DecryptionText;
//        System.out.println(DecryptionText);
    }
    /*  对文件流进行读取与写入
     *   实现文件的加密与解密  */
    public static void Encryption(String inputFileName, String outputFileName, StringBuffer key) throws Exception {
        try {
            //传入文件路径fileName
            File inputfile = new File(inputFileName);
            if (!inputfile.exists()) {
                throw new Exception("该文件不存在");
            }
            File outfile = new File(outputFileName);
            if (!outfile.exists()) {
                outfile.createNewFile();
            }
            FileInputStream in = new FileInputStream(inputFileName);
            FileOutputStream out = new FileOutputStream(outputFileName);
            //每次读8个字节，放到数组里
            byte[] bytes = new byte[8];

            int count = 0;// 记录每次读取的字节的个数以及是否读取完
            while (count != -1) {
                count = in.read(bytes);
                System.out.println("明文： " + byte8TOBit64(bytes));
                System.out.println(count);

                if (count != 8) {
                    for (int i = count; i < 7; i++) {
                        bytes[i] = '\0';
                    }
                    bytes[7] = (byte) (8 - count);
                }
                StringBuffer cipherString = new StringBuffer(Encryption(byte8TOBit64(bytes), key));
                System.out.println("密文： " + cipherString);
//                System.out.println(bit64TOByte8(cipherString));
                out.write(bit64TOByte8(cipherString));
//                byte[] cipherByte = Encryption(byte8TOBit64(bytes), key);
//                out.write(cipherByte);
            }
            in.close();
            out.close();
        } catch (Exception e) {
        }
    }
    public static void Decryption(String inputFileName, String outputFileName, StringBuffer key) throws Exception {
        try {
            //传入文件路径fileName
            File inputfile = new File(inputFileName);
            if (!inputfile.exists()) {
                throw new Exception("该文件不存在");
            }
            long fileLong = inputfile.length(); // 记录文件字节长度，方便判断最后一个字节的填充

            File outfile = new File(outputFileName);
            if (!outfile.exists()) {
                outfile.createNewFile();
            }
            FileInputStream in = new FileInputStream(inputFileName);
            FileOutputStream out = new FileOutputStream(outputFileName);
            //每次读8个字节，放到数组里
            byte[] bytes = new byte[8];
            int count = 0;// 记录每次读取的字节的个数以及是否读取完
            while (in.read(bytes) != -1) {
                count += bytes.length;
                System.out.println(count);
                System.out.println("加密文件" + byte8TOBit64(bytes));
                StringBuffer plainString = Decryption(byte8TOBit64(bytes), key);
                if (count == fileLong && (int) bit64TOByte8(plainString)[7] < 8) {
                    for (int i = 0; i < 8 - (int) bit64TOByte8(plainString)[7]; i++) {
                        out.write(bit64TOByte8(plainString)[i]);
                    }
                } else {
                    out.write(bit64TOByte8(plainString));
                }
                System.out.println("解密： " + plainString);
            }
            in.close();
            out.close();
        } catch (Exception e) {
        }
    }
    public static void main(String[] args) throws Exception {
        long stime = System.currentTimeMillis();
        Encryption("./test/1.txt","加密.txt", new StringBuffer("ddddaaaa"));
        Decryption("加密.txt","解密.txt", new StringBuffer("ddddaaaa"));
        // 结束时间
        long etime = System.currentTimeMillis();
        // 计算执行时间
        System.out.printf("执行时长：%d 毫秒.", (etime - stime));
    }
}
